/*
 * Created on Mar 8, 2010
 */
package com.mgmaps.gps;

import java.io.IOException;
import com.mgmaps.app.APPCanvas;
import com.mgmaps.app.APPSettings;
import com.mgmaps.app.GPSPlace;
import com.mgmaps.utils.ExtLogger;
import com.nutiteq.components.WgsPoint;

public class GPS2 {
  GPSReceiver recv;
  private int recvIndex;
  
  // receiver indexes
  public static final int GR_NONE = -1;
  public static final int GR_INTERNAL = 0;
  public static final int GR_BLUETOOTH = 1;
  public static final int GR_SERIAL = 2;
  public static final int GR_TCP = 3;
  public static final int GR_ANDROID = 4;
  
  GPSPlace place;
  private GPSTimerTask task;
  
  private class GPSTimerTask extends Thread {
    private boolean quitting;
    
    private boolean firstFix;
    private WgsPoint oldPoint;
    private long lastUpdateTime;
    
    public GPSTimerTask(boolean ff) {
      oldPoint = null;
      lastUpdateTime = 0;
      firstFix = ff;
      
      start();
    }
    
    public synchronized void notifyExit() {
      quitting = true;
      notify();
    }
    
    public void run() {
      while (!quitting) {
        if (APPSettings.gpsEnabled && recv != null)
          try {
            WgsPoint p = recv.getPoint();
            if (p != null) {
              long currentTime = System.currentTimeMillis();
              if (currentTime - lastUpdateTime > APPSettings.gpsFixCutoff || !p.equals(oldPoint)) {
                updatePos(p);
                lastUpdateTime = currentTime;
                oldPoint = p;
              }
            }
          }
          catch (Exception ex) {
            ExtLogger.debug("Exception updating position, normal at application startup");
          }
          
        synchronized(this) {
          try {
            wait(1000);
          }
          catch (InterruptedException ie) {
          }
        }
      }
    } // end run
    
    /**
     * Update map position.
     * 
     * @param p new map position (lon/lat)
     */
    protected void updatePos(WgsPoint p) {
      place.setWgs(p);
      APPCanvas.instance.notifyGPSUpdate(firstFix);
      firstFix = false;
    }
  } // end timer task
  
  
  public GPS2(boolean ff) {
    // GPS icon
    try {
      place = new GPSPlace();
    }
    catch (IOException ie) {
      ExtLogger.handle(ie, true, "Error loading GPS icon");
    }
    
    // GPS receiver
    recvIndex = -1;
    recv = null;
    setReceiver(false);
    
    // run the GPS timer task every second
    task = new GPSTimerTask(ff);
  }
  
  public boolean setReceiver(boolean force) {
    final int newIndex = getGPSReceiverIndex(APPSettings.gpsDevice);
    
    // nothing to do?
    if (newIndex == recvIndex && !force)
      return false;
    
    try {
      // deactivate
      if (recvIndex >= 0) {
        recv.deactivate();
        recv = null;
        recvIndex = -1;
      }
      
      // check that the receiver is supported and > 0, return false if not
      if (newIndex < 0)
        return false;
      
      // create receiver
      GPSReceiver newRecv = createReceiver(newIndex);
      if (newRecv == null)
        return false;
    
      recvIndex = newIndex;
      recv = newRecv;
      recv.activate();
      if (task != null)
        task.run();
      return true;
    }
    catch (Exception ex) {
      ExtLogger.handle(ex, false, "Error setting active GPS receiver");
      return false;
    }
  }
  
  /**
   * Instantiate a GPSReceiver.
   * @param num receiver number
   * @return GPSReceiver object
   */
  private GPSReceiver createReceiver(int num) {
    GPSReceiver r = null;
    
    switch(num) {
      case GR_INTERNAL:
        try {
          r = (GPSReceiver) Class.forName("com.mgmaps.gps.GPSInternal").newInstance();
        }
        catch (Exception ex) {
          ExtLogger.handle(ex, false, "Error instantiating GPSInternal");
          r = null;
        }
        break;
        
      /*
      case GR_ANDROID:
        try {
          r = (GPSReceiver) Class.forName("com.mgmaps.gps.GPSAndroid").newInstance();
        }
        catch (Exception ex) {
          ExtLogger.handle(ex, false, "Error instantiating GPSAndroid");
          r = null;
        }
        break;
      */
        
      case GR_BLUETOOTH:
        try {
          r = (GPSReceiver) Class.forName("com.mgmaps.gps.GPSBluetooth").newInstance();
        }
        catch (Exception ex) {
          ExtLogger.handle(ex, false, "Error instantiating GPSBluetooth");
          r = null;
        }
        break;
        
      case GR_SERIAL:
        try {
          r = (GPSReceiver) Class.forName("com.mgmaps.gps.GPSSerial").newInstance();
        }
        catch (Exception ex) {
          ExtLogger.handle(ex, false, "Error instantiating GPSSerial");
          r = null;
        }
        break;
    }
    
    return r;
  }
  
  /**
   * Get GPS receiver index given gpsDevice name.
   * @param name gpsDevice
   * @return 0 (internal), 1 (bluetooth), 2 (serial) -- or -1 if other 
   */
  public static int getGPSReceiverIndex(String name) {
    if (name == null)
      return GR_NONE;
    else if (APPSettings.hasLAPI && name.equals("Internal"))
      return GR_INTERNAL;
    else if (APPSettings.hasBT && name.startsWith("btspp://"))
      return GR_BLUETOOTH;
    else if (APPSettings.hasSerial && name.startsWith("comm:"))
      return GR_SERIAL;
    else if (APPSettings.isAndroid && name.equals("Android"))
      return GR_ANDROID;
    else
      return GR_NONE;
  }
  
  /**
   * Called when the application is about to exit, stops the threads.
   */
  public void notifyExit() {
    if (recv != null)
      recv.notifyExit();
    if (task != null)
      task.notifyExit();
  }
  
  /**
   * Get the current GPS place object.
   * @return the GPSPlace object.
   */
  public GPSPlace getPlace() {
    return place;
  }
  
  /**
   * Get current receiver.
   * @return recv
   */
  public GPSReceiver getReceiver() {
    return recv;
  }
  
  /**
   * Enable GPS sync and re-run update.
   */
  public void enableSync() {
    APPSettings.gpsSyncEnabled = true;
    synchronized(task) {
      task.notify();
    }
  }
  
}
